Completed
Push — master ( 5dc4b7...3c0927 )
by Marcelo
11s
created

vm.js ➔ ... ➔ ???   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
nc 3
dl 0
loc 10
rs 9.4285
nop 2
1
import { NodeVM } from 'vm2';
2
import Promise, { reject, resolve } from 'bluebird';
3
import {
4
    propOr,
5
    tryCatch,
6
    type
7
} from 'ramda';
8
import { compileHTML } from './compiler';
9
import { upsert } from './db';
10
import { translator } from './i18n';
11
import { evaluateModules } from './module';
12
13
/**
14
 * Returns an instance of the Rung virtual machine
15
 *
16
 * @author Marcelo Haskell Camargo
17
 * @param {Object} translator - Map of strings to translate
0 ignored issues
show
Documentation introduced by
The parameter translator does not exist. Did you maybe forget to remove this comment?
Loading history...
18
 * @return {NodeVM}
19
 */
20
export function createVM(strings) {
21
    const vm = new NodeVM({
22
        require: {
23
            external: true
24
        }
25
    });
26
27
    vm.freeze(compileHTML, '__render__');
28
    vm.freeze(translator(strings), '_');
29
30
    return vm;
31
}
32
33
/**
34
 * Runs an extension on a virtualized environment and returns its result as
35
 * native JS data
36
 *
37
 * @author Marcelo Haskell Camargo
38
 * @param {String} name - The unique identifier to track the extension
39
 * @param {String} source - ES6 source to run
40
 * @param {Object} strings - Object containing the strings to translate
41
 * @param {String[][]} modules - Map of modules with [filename, source]
42
 * @return {Promise}
43
 */
44
function runInSandbox(name, source, strings = {}, modules = []) {
45
    const evaluate = tryCatch(() => {
46
        const vm = createVM(strings);
47
48
        // Pre-evaluate and inject in vm all necessary modules
49
        vm.options.require.mock = evaluateModules(vm, modules);
50
51
        // Run with all modules! :)
52
        const result = vm.run(source, `${name}.js`);
53
        return resolve(propOr(result, 'default', result));
54
    }, reject);
55
56
    return evaluate();
57
}
58
59
/**
60
 * Tries to get the parameter types by running the script to get config.params
61
 *
62
 * @author Marcelo Haskell Camargo
63
 * @param {Object} extension - Must contain name and source
64
 * @param {Object} strings - Object with strings to translate
65
 * @param {String[][]} modules - Object with modules name and source
66
 * @return {Promise}
67
 */
68
export function getProperties(extension, strings, modules) {
69
    return runInSandbox(extension.name, extension.source, strings, modules)
70
        .then(propOr({}, 'config'));
71
}
72
73
/**
74
 * Runs an extension with a context (with parameters) and gets the alerts.
75
 * The result may be a string, a nullable value, an array...
76
 *
77
 * @author Marcelo Haskell Camargo
78
 * @param {Object} extension - Object containing name and source
79
 * @param {Object} context - Context to pass to the main function
80
 * @param {Object} strings - Object with strings to translate
81
 * @param {String[][]} modules - Modules with [filename, source]
82
 * @return {Promise}
83
 */
84
export function runAndGetAlerts(extension, context, strings, modules) {
85
    return runInSandbox(extension.name, extension.source, strings, modules)
86
        .then(app => {
87
            const runExtension = ~new Promise((resolve, reject) => {
88
                if (type(app.extension) !== 'Function') {
89
                    return reject(new TypeError('Expected default exported expression to be a function'));
90
                }
91
92
                // Async vs sync extension
93
                return app.extension.length > 1
94
                    ? app.extension.call(null, context, resolve)
95
                    : resolve(app.extension.call(null, context));
96
            });
97
98
            return runExtension();
99
        })
100
        .tap(result => upsert(extension.name, result.db));
101
}
102